package cn.micai.base.thread;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 描述：有状态 Servlet   线程不安全
 * <p>
 *     在 Servlet 中定义了一个实例变量 result，Servlet 把它的值进行输出。当只有一个用户访
 问该 Servlet 时，程序会正常的运行，但当多个用户并发访问时，就可能会出现其它用户的信息显示在另外一些用户的浏览器上的问题 。这是一个严重的问题。
 为了突出并发问题，便于测试、观察，我们在回显用户信息时执行了一个延时的操作

 从运行结果可以看出，两个请求显示了相同的计算结果，也就是说，因为两个线程访问
 了共同的有状态的 Servlet，其中一个线程的计算结果覆盖了另外一个线程的计算结果。从程
 序分析可以看出第一个线程在输出 result 时，暂停了一段时间，那么它的值就被第二个线程
 的计算结果所覆盖，两个请求输出了相同的结果。这就是潜在的线程不安全性。

 Servlet 的线程安全问题只有在大量的并发访问时才会显现出来，并且很难发现，因此在
 编写 Servlet 程序时要特别注意。线程安全问题主要是由实例变量造成的，因此在 Servlet 中
 应避免使用实例变量。如果应用程序设计无法避免使用实例变量，那么使用同步来保护要使
 用的实例变量，但为保证系统的最佳性能，应该同步可用性最小的代码路径
 *
 * @author: 赵新国
 * @date: 2018/5/11 16:50
 */
public class StatefulServlet extends HttpServlet {

    int result = 0;

    public StatefulServlet() {
        super();
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String s1 = request.getParameter("num1");
        String s2 = request.getParameter("num2");
        /**
         * 要解决线程不安全性，其中一个主要的方法就是取消 Servlet 的实例变量，变成无状态的
         Servlet。另外一种方法是对共享数据进行同步操作。使用 synchronized 关键字能保证一次只
         有一个线程可以访问被保护的区段，同步后的 Servlet 如下
         */
        synchronized (this) {
            if (s1 != null && s1 != null) {
                result = Integer.parseInt(s1) * Integer.parseInt(s2);
            }
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            PrintWriter out = response.getWriter();
            out.print(result);
            out.close();
        }
    }
}
